-module(cloap_handler).
-include("cloap_record.hrl").
-define(R2P(Record), record_to_proplist(#Record{} = Rec) ->
           lists:zip(record_info(fields, Record), tl(tuple_to_list(Rec)))).



% ================================================================================
% @doc 常规操作
% ================================================================================
-define(INDEX_HANDLE(Dim),
        handle(??Dim ++ ".index", _Params) ->
               [record_to_proplist(X) || X <- cloap_store:find_all(Dim)]).
-define(INDEX(Dim),
    [record_to_proplist(X) || X <- cloap_store:find_all(Dim)]).
-define(DESTROY_HANDLE(Dim),
        handle(??Dim ++ ".destroy", Params) ->
               Id = binary_get_value(id, Params),
               case cloap_store:delete(Dim, Id) of
                   ok   ->  #{result => ok, id => Id};
                   _    ->  #{result => error, message => <<"删除失败">>}
               end).




% ================================================================================
% @doc 针对不同的维度，定义不同的参数，从而模块化维度的操作
% ================================================================================
-define(LDAP_HANDLE_INDEX(Dim),
        handle(??Dim ++ ".index", _Params) ->
               user_store:find_all(Dim)).
-define(LDAP_HANDLE_CREATE(Dim, Args),
        handle(??Dim ++ ".create", Params) ->
               Values  = [binary_get_value(V, Params) || {_K, V} <- Args],
               Params1 = lists:zip([K || {K, _V} <- Args], Values),

               {ok, Id} = user_store:add(Dim, Params1),
               [H|_] = user_store:find(Dim, Id),
               H).
-define(LDAP_HANDLE_DESTROY(Dim),
        handle(??Dim ++ ".destroy", Params) ->
               Id = binary_get_value(id, Params),
               {ok, Id} = user_store:destroy(Dim, Id),

               ok({id, Id})).




-export([init/2,
         websocket_handle/3, websocket_info/3
        ]).
-export([handle/2]).


?R2P(job);
?R2P(app);
?R2P(uddi);
?R2P(acl);
?R2P(config).



ok({id, V}) ->
    #{result => ok, id => V}.


init(Req, Opts) ->
    {cowboy_websocket, Req, Opts}.

websocket_handle({text, Msg}, Req, State) ->
    Params = try
                 jsx:decode(Msg)
             catch
                 _:DecodeReason ->
                     lager:error("websocket handle decode error, reason: ~p ~n, params: ~p", [DecodeReason, Msg])
             end,



    Event  = binary_to_list(binary_get_value(event, Params)),
    OriginData = binary_get_value(data,  Params),
    LoginUser = case cloap_app:use_sso() of
                    false -> <<"system">>;
                    true ->
                        {LoginUser1, _} = cowboy_cas_client:user(Req),
                        LoginUser1
                end,

    Data   = case cloap_app:use_sso() of
                 false -> OriginData;
                 true  ->
                     try
                         [{<<"user_login">>, LoginUser} | OriginData]
                     catch
                         _:_ -> OriginData
                     end
             end,

    case Event of
        "user.me" ->
            case cloap_app:use_sso() of
                true ->
                    {User, _}        = cowboy_cas_client:user(Req),
                    {reply, {text, wrapper(Event, #{login => User})}, Req, State};
                false ->
                    {reply, {text, wrapper(Event, #{login => <<>>})}, Req, State}
            end;
        _ ->
            Result = try handle(Event, Data) of
                         Value -> 
                             cloap_event:action(LoginUser, <<"admin">>, list_to_binary(Event), OriginData),
                             wrapper(Event, Value)
                     catch
                         _:Reason   ->
                             lager:error("Websocket Handler Error: ~p", [Reason]),
                             wrapper(Event, #{result => error, message => <<"Error occured">>})
                     end,
            {reply, {text, Result}, Req, State}
    end;
websocket_handle(_Data, Req, State) ->
    {ok, Req, State}.

websocket_info({timeout, _Ref, Msg}, Req, State) ->
    {reply, {text, Msg}, Req, State};
websocket_info(_Info, Req, State) ->
    {ok, Req, State}.



handle("acl.index", _Params) ->
    CheckFun = fun(X) ->
                       case X#acl.appid of
                           <<"admin">> -> true;
                           AppId ->
                               case cloap_store:find(app, AppId) of
                                   [] -> false;
                                   [_App] -> true
                               end
                       end
               end,

    Acls = [record_to_proplist(Rec) || Rec <- cloap_store:find_all(acl), CheckFun(Rec) =:= true],

    lists:map(fun(X) ->
                      case proplists:get_value(appid, X) of
                          <<"admin">> ->
                              [{app, record_to_proplist(#app{name = <<"系统管理"/utf8>>})} | X];
                          AppId ->
                              [App] = cloap_store:find(app, AppId),
                              [{app, record_to_proplist(App)} | X]
                      end
              end, Acls);
handle("acl.edit", Params) ->
    AppId    = binary_get_value(appid, Params),
    Allows   = binary_get_value(allow, Params),
    Denies   = binary_get_value(deny,  Params),
    Default  =
        case binary_get_value(default, Params) of
            <<"allow">> -> allow;
            _           -> deny
        end,

    [Acl] = cloap_store:find(acl, AppId),
    NewAcl = Acl#acl{appid = AppId, allow = Allows, deny = Denies, default = Default},
    cloap_store:insert(NewAcl),
    record_to_proplist(NewAcl);



% --------------------------------------------------------------------------------
% @doc 工作分配
% --------------------------------------------------------------------------------
handle("job.index", _Params) ->
    Os = [proplists:get_value(dn, X) || X <- user_store:find_all(organ)],
    Ps = [proplists:get_value(dn, X) || X <- user_store:find_all(position)],

    [record_to_proplist(X) || X <- cloap_store:find_all(job), lists:member(X#job.organ, Os), lists:member(X#job.subject, Ps)];
handle("job.create", Params) ->
    Organ = binary_get_value(organ, Params),
    Subject = binary_get_value(subject, Params),
    Assign = binary_get_value(assign, Params),
    Mode = binary_get_value(mode, Params, <<"override">>),

    Rec = case cloap_store:find(job, [{organ, Organ}, {subject, Subject}]) of
              not_found ->
                  Id = uuid:generate(),
                  #job{id=Id, organ=Organ, subject=Subject, assign=Assign};
              Value ->
                  case Mode of
                      <<"append">> ->
                          Assign1 = lists:usort(Assign ++ Value#job.assign),
                          Value#job{organ=Organ, subject=Subject, assign=Assign1};
                      <<"delete">> ->
                          Assign2 = lists:usort(Value#job.assign -- Assign),
                          Value#job{organ=Organ, subject=Subject, assign=Assign2};
                      _ ->
                          Value#job{organ=Organ, subject=Subject, assign=Assign}
                  end
          end,
    cloap_store:insert(Rec),
    record_to_proplist(Rec);
handle("job.edit", Params) ->
    Id      = binary_get_value(id,      Params),
    Organ   = binary_get_value(organ,   Params),
    Subject = binary_get_value(subject, Params),
    Assign  = binary_get_value(assign,  Params),

    Rec = #job{id=Id, organ=Organ, subject=Subject, assign=Assign},
    cloap_store:insert(Rec),
    record_to_proplist(Rec);
?DESTROY_HANDLE(job);



% --------------------------------------------------------------------------------
% @doc 组织机构
% --------------------------------------------------------------------------------
?LDAP_HANDLE_INDEX(organ);
?LDAP_HANDLE_CREATE(organ, [{ou, id}, {cn, name}, {member, member}]);
?LDAP_HANDLE_DESTROY(organ);

% --------------------------------------------------------------------------------
% @doc 组织分类
% --------------------------------------------------------------------------------
handle("rating.index", _Params) ->
    lists:map(
      fun(X) -> split_member(X) end,
      user_store:find_all(rating)
     );
?LDAP_HANDLE_CREATE(rating, [{ou, id}, {cn, name}, {member, member}]);
?LDAP_HANDLE_DESTROY(rating);

% --------------------------------------------------------------------------------
% @doc 岗位设置
% --------------------------------------------------------------------------------
?LDAP_HANDLE_INDEX(position);
?LDAP_HANDLE_CREATE(position, [{ou, id}, {description, name}]);
?LDAP_HANDLE_DESTROY(position);







handle("user.info", Params) ->
    Id = binary_get_value(id, Params),
    user_store:find_one(Id);
handle("user.index", _Params) ->
    user_store:find_all();
handle("user.destroy", Params) ->
    Id = binary_get_value(id, Params),
    user_store:destroy(user, Id),
    ok({id, Id});
handle("user.create", Params) ->
    Uid     = binary_get_value(login, Params),
    Name    = binary_get_value(name, Params),
    Passwd  = binary_get_value(password, Params),
    Mail    = binary_get_value(mail, Params),
    Mobile  = binary_get_value(mobile, Params),

    Options =
        (case Mail of <<"undefined">> -> []; _ -> [{"mail", [Mail]}] end) ++
        (case Mobile of <<"undefined">> -> []; _ -> [{"mobile", [Mobile]}] end),

    {ok, _} = user_store:add_user(binary_to_list(Uid),
                                  [
                                   {"uid", [Uid]}, {"sn", [Name]}, {"cn", [Name]}, {"displayName", [Name]},
                                   {"userPassword", [Passwd]},
                                   {"objectClass", ["inetOrgPerson", "organizationalPerson", "person", "top"]}
                                  ] ++ Options),
    user_store:find_one(binary_to_list(Uid));
handle("user.edit", Params) ->
    Uid     = binary_get_value(login, Params),
    Name    = binary_get_value(name, Params),
    Mail    = binary_get_value(mail, Params),
    Mobile  = binary_get_value(mobile, Params),

    {ok, _} = user_store:update_user(binary_to_list(Uid),
        [ {"cn", [Name]}, {"sn", [Name]}, {"displayName", [Name]},
          {"mail", [Mail]}, {"mobile", [Mobile]}
        ]),
    user_store:find_one(binary_to_list(Uid));
handle("user.reset.password", Params) ->
    Uid     = binary_get_value(login, Params),
    Passwd  = binary_get_value(password, Params),

    {ok, _} = user_store:update_user(binary_to_list(Uid), [{"userPassword", [Passwd]}]),
    user_store:find_one(binary_to_list(Uid));




handle("config.index", _Params) ->
    [record_to_proplist(Rec) || Rec <- cloap_store:find_all(config)];
handle("config.create", Params) ->
    [Key, Descr, Value] =
        [ binary_get_value(Elem, Params, <<>>) || Elem <- [key, descr, value] ],
    Rec = case cloap_store:find(config, Key) of
              [] -> #config{key = Key, value = Value, descr = Descr, mustbe = <<"false">>};
              [OriginRec] -> OriginRec#config{value = Value, descr = Descr}
          end,
    cloap_store:insert(Rec),
    record_to_proplist(Rec);
handle("config.destroy", Params) ->
    Id = binary_get_value(id, Params),
    case cloap_store:delete(config, Id) of
        ok   ->  #{result => ok, key => Id};
        _    ->  #{result => error, message => <<"删除配置失败">>}
    end;


handle("app.me", Params) ->
    Apps = [cloap_module_handler:admin_app() | cloap_store:find_all(app)],
    User = binary_get_value(user_login, Params),

    case cloap_app:use_sso() of
        false ->
            [record_to_proplist(Rec) || Rec <- Apps];
        true ->
            Allows = cloap_acl:allow_apps(User),
            [record_to_proplist(Rec) || Rec <- lists:filter(fun(X) -> lists:member(X#app.appid, Allows) end, Apps)]
    end;
?INDEX_HANDLE(app);
handle("app.create", Params) ->
    RootUrl = binary_get_value(url, Params),
    Url = binary_to_list(RootUrl) ++ "/cloap/app/info",
    case httpc:request(Url) of
        {error, Reason} ->
            #{result => error, message => Reason};
        {ok, {_Status, _Headers, Body}} ->
            AppParam = jsx:decode(list_to_binary(Body)),

            [AppId, Name, Author, Release, ReleasedAt, Descr, Settings] =
                [ proplists:get_value(list_to_binary(atom_to_list(Attr)), AppParam) ||
                    Attr <- [app_id, name, author, release, released_at, descr, settings] ],

            App = #app{
                     appid = AppId, name = Name, descr = Descr,
                     author = Author, release = Release, released_at = ReleasedAt,
                     settings = Settings, callback = #{ url => RootUrl, method => rest }
                    },

            cloap_store:insert(App),
            cloap_store:insert(#acl{appid = AppId}),
            record_to_proplist(App)
    end;
handle("app.destroy", Params) ->
    Id = binary_get_value(id, Params),
    case cloap_store:delete(app, Id) of
        ok   ->  #{result => ok, id => Id};
        _    ->  #{result => error}
    end;
handle("app.module", Params) ->
    Id = binary_get_value(id, Params),
    {ok, Html} = module_homepage_dtl:render([{module, Id}]),
    #{result => ok, id => Id, homepage => iolist_to_binary(Html)};


handle("uddi.index", _Params) ->
    lists:foreach(
        fun(X) ->
            case cloap_store:find(uddi, X#app.appid) of
                [] -> cloap_store:insert(#uddi{appid = X#app.appid});
                _  -> ok
            end
        end,
        cloap_store:find_all(app)),
    ?INDEX(uddi);

handle("uddi.edit", Params) ->
    AppId    = binary_get_value(appid, Params),
    Url      = binary_get_value(url, Params),

    [Rec] = cloap_store:find(uddi, AppId),
    NewRec = Rec#uddi{url = Url},
    cloap_store:insert(NewRec),
    record_to_proplist(NewRec);


handle(Event, _Params)	->
    lager:error("No Action ~p Found ~n", [Event]),
    #{message => <<"No action found.">>}.


wrapper(Event, Value) ->
    jsx:encode(#{event => list_to_binary(Event), data => Value}).

binary_get_value(Key, List, Def) ->
    Key2 = list_to_binary(atom_to_list(Key)),
    case proplists:get_value(Key2, List, Def) of
        undefined ->
            <<"undefined">>;
        Val -> Val
    end.
binary_get_value(Key, List) ->
    binary_get_value(Key, List, undefined).








% --------------------------------------------------------------------------------
% @doc 分离成员
% --------------------------------------------------------------------------------
split_member(Rec) ->
    Members = proplists:get_value(member, Rec, []),
    Keys = lists:usort(
             lists:map(
               fun(X) ->
                       [[Key]] = re:replace(X, ".+?=.+?,.+?=(.+?),.*", "\\1"),
                       Key
               end,
               Members)),
    lists:zip(
      [ list_to_atom(binary_to_list(K)) || K <- Keys ],
      [ lists:filter(fun(X1) -> [[Y]] = re:replace(X1, ".+?=.+?,.+?=(.+?),.*", "\\1"), Y =:= Key1 end, Members) || Key1 <- Keys ]
      ) ++ proplists:delete(member, Rec).
